
javascript 学习笔记

代码段
1 | <!-- 一个script标签就是一个代码段--> |
预编译(预解析)
JS 代码在执行时可分为两个阶段
- 预编译(预解析)
- 代码执行,即一行一行执行,等到解析结束之后,才会尽心那个代码执行,也就是说浏览器把 JS 代码进行加工之后再去执行,加工的过程就是预编译
预编译期间做了什么?
- 声明提升:
var变量提升,但是之提升了声明没有赋值。function函数整体提升。提升到代码段最前面 - 如果在函数内部的局部变量,就提升但函数内部的最前面
ps:var 变量只提升声明,函数提升的是函数整体
代码执行产生 EC 和 GO
JS 在内存中主要看内存的栈区和堆区,其中基本数据类型存储在栈区,引用数据类型(函数)存储在堆区,地址存储在栈区
JS 代码主要分两类
全局代码:默认进入script标签就会执行全局代码
函数代码:一个函数就是一个局部函数代码
1 | <script> |
- 全局代码执行时,会产生全局执行上下文
Execution Context Globle(ECG) - 每调用函数时就产生一个局部的执行上下文
- 执行上下文产生即放入一个栈空间:执行上下文栈
Execution Context Stack(ECS) - 当函数调用完毕,函数的
EC出栈,当ECG执行完毕,ECG也要出栈
执行上下文EC的作用:给代码提供数据,代码中需要的数据都从EC中的栈中读取
JS 代码在执行时。会在堆内存中创建一个全局对象Global Object(GO),这个GO也就是Window,是一个全局对象,对象也就是属性的无序集合。很多API都是Window的属性,例如:Date,Array,String,Number,SetTimeout...
执行全局代码时会在
ECS中产生ECG,在堆内存则会有一个GO(Window)代码执行前
ECG中包含两个部分:- 变量对象
VO(此时就等同于GO) - 全局代码执行
- 变量对象
调用函数
fn()时,产生一个EC(fn)入栈,包含三个部分解析函数称为
AST树结构时,会创建一个活动对象Activation Object(AO),AO中包含形参、arguments、函数定义和指向函数对象、定义的变量作用域链
scopchain:由VO(在函数中就是AO的对象)和父级VO组成,查找时会逐层查找。ps:所谓作用域链就是数据在EC中查找的过程,找一个数据,先在自己的EC中找,如果找不到就去父的EC中找,直到ECG,如果还找不到就报错。this绑定的值
1 | <script> |
再来个栗子
1 | <script> |
变量和闭包
变量
加 var 和不加 var 的变量区别
需要注意的是:
- 加
var的变量在预编译的期间会提升,但是不加var的变量在预编译的时候不会提升 - 不管是否加
var,只要是全局变量,在非严格模式下都会挂载到GO上 - 加
var的变量既可以做全局变量也可以局部变量,不加var只能全局变量
平时做项目基本不用var,更不要不定义变量类型
1 | <script> |
使用 let 声明变量
使用let声明的变量没有提升,或者可以理解为变量提升了,但是没有赋值,也就是不能直接访问
1 | <script> |
let+{}可以形成块级作用域,块级作用域中定义的变量,只能在块中使用
1 | <script> |
使用let声明的变量,并不会挂载到GO上
1 | <script>let a=1; console.log(window.a); //undefined</script> |
let不能重复声明
1 | <script> |
所以项目中声明变量基本使用let,弥补了var声明变量的缺点
使用 const 声明变量(常量)的特点
- 声明不能被修改
- 使用
const声明变量时,必须赋值,不然会报语法错误 const声明的变量也不会提升const和{}也可以形成块级作用域const声明的变量也不会挂载到GO上
总结:在项目中,定义变量用 let,定义常量用 const
例题:
1 | <script> |
1 | <script> |
1 | <script> |
闭包
先看代码
1 | <script> |
画出部分堆栈图

按照堆栈函数调用来说,当 A 函数调用完毕,ECA 出栈,ECA 中分配的占内存,也就是 i,需要回收。但是,由于 ECG 中的一个 y,引用着这个 0x345 的堆空间,0x345 堆空间中存在着 ECA 中 i 的栈空间,所以说,0x345 这个堆和 ECA 中的 i 是不能被释放。一个不能被回收释放掉的栈空间,叫做闭包,i 会常驻内存,会造成内存空间泄露。
然后再调用函数 y,执行上下文ECy入栈console.log(y),所以再去作用域链里寻找i,作用域链里包含自身VO和父VO,最终在父VO里找到i为2 输出。调用之后,出栈销毁。
然后调用函数B,执行上下文ECB,包含VO:AO,i的值为20,然后调用y又产生一个执行上下文ECy2,ECy2中只有console.log(i),所以在作用域链中寻找,在父VO中找到i=2
所以最终控制台输出 2 2
闭包:一个不能被回收的栈内存,就可以被成为闭包
作用:
- 保护 EC 中的变量,外界不能直接访问
- 可以让我们像使用全局变量一样使用局部变量,延长了变量的生命周期
this
this:字面意思是“这个”的意思
this和书写的位置没有关系,和调用的方式有关系,是产生执行上下文EC的时候动态绑定的
this的绑定规则:
- 默认绑定
- 隐式绑定
- 显式绑定
new绑定
默认绑定
独立函数调用就是所谓的默认绑定,独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用
1 | <script> |
以上this指向的都是window
隐式绑定
另外一种比较常见的调用方式是通过某个对象进行调用的,他就是它的调用位置中,是通过某个对象发起的函数调用
1 | <script> |
显式绑定
JS中,函数有多种角色
- 普通函数
- 对象中的方法
- 对象(属性的无序集合,内部有很多默认属性和方法,
call,apply,bind...) - 类(构造器,构造函数)
call()
- 显示绑定
this - 让
fn()执行
1 | <script> |
apply()
apply作用和call一样,传参方式不同,需要把参数放到一个数组
1 | fn.apply(obj, [222, 333]); |
bind()
作用和call()一样但是不会让函数执行,返回this之后的新函数
1 | let newFn = fn.bind(obj, 222, 333); |
显式绑定总结:
call fn.call(obj,1,2)显式绑定this,让fn()执行 ,也能传参apply fn.apply(obj,[1,2])显式绑定this,让fn()执行,参数放到数组中bind fn.bind(obj,1,2)显式绑定this,返回绑定this后的新函数,也能传参
注意:
1 | <script> |
new 绑定
JS中,函数有多种角色
- 普通函数
- 对象中的方法
- 函数也是对象
- 函数也是类(构造函数/构造器),通常情况下首字母大写
1 | <script> |
this 绑定的优先级
1 | <script> |
- 默认绑定的优先级最低
- 显示绑定的优先级高于隐式绑定
1 | <script> |
- new 绑定优先级高于隐式绑定
1 | <script> |
- new 绑定高于显式绑定
1 | <script> |
- new 绑定不能和 call 和 apply 比较
1 | <script> |
箭头函数
1 | <script> |
箭头函数中的 this
箭头函数中的 this 需要往上找一层
1 | <script> |
对象
1 | <ul> |
一个真实的dom元素,本质就是一个对象,这个对象的属性非常多,操作这个对象,性能就很低,由此JQuery被取代,JQuery操作的就是DOM元素,vue、react操作的就是虚拟DOM元素,虚拟DOM元素的属性灭有那么多
new 的原理
new做了什么:
- 在构造器内部创建一个新的对象
- 这个歌对象的
prototype属性会被复制为该构造函数的prototype属性 - 让构造器中的
this指向这个对象 - 执行构造器中的代码
- 如果构造器中没有返回对象,则返回上面创建出来的对象
1 | //函数在JS中有多种角色,其中一种角色就是构造器 |
原型和原型链
公有属性和私有属性
只要是一个对象,身上必定有一个属性叫__proto__,__proto__是属性名,叫隐式原型
1 | /* |
1 | let obj = { |
a.b
先去自己的EC(执行上下文)中找a,如果找不到,就去父的EC中找,如果还找不到,就去父的父中找,直到找到ECG(全局执行上下文),如果还找不到,error: a is not defined,整个查找机制就叫做作用域链。
找b,先找自己的私有属性,如果找不到,就沿__proto__去公有属性中找,如果公有属性一直找不到,得到undefined,因为查找一个对象上不存在的属性,得到undefined,叫原型链
1 | let obj = { |
in
判断一个属性是否的呼吁某个对象(私有、公有)
1 | let arr = []; |
hasOwnProperty
判断是否是私有
隐式原型和显式原型
例:
1 | let arr1 = new Array("wc", "xq"); |
原型链
作用域链:是在EC中查找数据的机制
原型链:实在对象上查找属性的机制
例如:
1 | let p = new Person("xxx", 18); |
总结
- 一切都是对象
- 对象是属性的无序集合
- 属性分为公有属性和私有属性
- 每个对象身上都有一个
__proto__(隐式原型) - 每个函数身上都有一个
prototype(显式原型) - 对象的隐式原型和函数的显式原型,指向一个对象,叫做原型对象
- 每一个原型对象身上有一个
constructor属性指向函数本身
- 标题: javascript 学习笔记
- 作者: liohi
- 创建于 : 2023-04-16 16:55:48
- 更新于 : 2023-04-19 22:26:59
- 链接: https://liohi.github.io/2023/04/16/javascript 学习笔记/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
变量和闭包